Completed
Push — master ( 7b524c...0d3c7c )
by Ruben de
05:15 queued 02:24
created

WalletSweeper.sweepWallet   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
c 3
b 0
f 0
nc 2
dl 0
loc 9
rs 9.6666
nop 0
1
var UnspentOutputFinder = require('./unspent_output_finder');
2
var bitcoin = require('bitcoinjs-lib');
3
var bip39 = require("bip39");
4
var CryptoJS = require('crypto-js');
5
var blocktrail = require('./blocktrail');
6
var EncryptionMnemonic = require('./encryption_mnemonic');
7
var Encryption = require('./encryption');
8
var walletSDK = require('./wallet');
9
var _ = require('lodash');
10
var q = require('q');
11
var async = require('async');
12
13
/**
14
 *
15
 * @param backupData
16
 * @param bitcoinDataClient
17
 * @param options
18
 * @constructor
19
 */
20
var WalletSweeper = function(backupData, bitcoinDataClient, options) {
21
    /* jshint -W071, -W074 */
22
    var self = this;
23
    this.defaultSettings = {
24
        network: 'btc',
25
        testnet: false,
26
        logging: false,
27
        bitcoinCash: false,
28
        sweepBatchSize: 200
29
    };
30
    this.settings = _.merge({}, this.defaultSettings, options);
31
    this.bitcoinDataClient = bitcoinDataClient;
32
    this.utxoFinder = new UnspentOutputFinder(bitcoinDataClient, this.settings);
33
    this.sweepData = null;
34
35
    // set the bitcoinlib network
36
    if (typeof options.network === "object") {
37
        this.network = options.network;
38
    } else {
39
        this.network = this.getBitcoinNetwork(this.settings.network, this.settings.testnet);
40
    }
41
42
    backupData.walletVersion = backupData.walletVersion || 2;   //default to version 2 wallets
43
44
    var usePassword = false;
45
46
    // validate backup data, cleanup input, and prepare seeds
47
    if (!Array.isArray(backupData.blocktrailKeys)) {
48
        throw new Error('blocktrail pub keys are required (must be type Array)');
49
    }
50
51
    switch (backupData.walletVersion) {
52
        case 1:
53
            if (typeof backupData.primaryMnemonic === "undefined" || !backupData.primaryMnemonic) {
54
                throw new Error('missing primary mnemonic for version 1 wallet');
55
            }
56
            if (typeof backupData.backupMnemonic === "undefined" || !backupData.backupMnemonic) {
57
                throw new Error('missing backup mnemonic for version 1 wallet');
58
            }
59
            if (typeof backupData.primaryPassphrase === "undefined") {
60
                throw new Error('missing primary passphrase for version 1 wallet');
61
            }
62
63
            // cleanup copy paste errors from mnemonics
64
            backupData.primaryMnemonic = backupData.primaryMnemonic.trim()
65
                .replace(new RegExp("\r\n", 'g'), " ")
66
                .replace(new RegExp("\n", 'g'), " ")
67
                .replace(/\s+/g, " ");
68
            backupData.backupMnemonic = backupData.backupMnemonic.trim()
69
                .replace(new RegExp("\r\n", 'g'), " ")
70
                .replace(new RegExp("\n", 'g'), " ")
71
                .replace(/\s+/g, " ");
72
        break;
73
74
        case 2:
75
        case 3:
76
            if (typeof backupData.encryptedPrimaryMnemonic === "undefined" || !backupData.encryptedPrimaryMnemonic) {
77
                throw new Error('missing encrypted primary seed for version 2 wallet');
78
            }
79
            if (typeof backupData.backupMnemonic === "undefined" || (!backupData.backupMnemonic && backupData.backupMnemonic !== false)) {
80
                throw new Error('missing backup seed for version 2 wallet');
81
            }
82
            //can either recover with password and password encrypted secret, or with encrypted recovery secret and a decryption key
83
            usePassword = typeof backupData.password !== "undefined" && backupData.password !== null;
84
            if (usePassword) {
85
                if (typeof backupData.passwordEncryptedSecretMnemonic === "undefined" || !backupData.passwordEncryptedSecretMnemonic) {
86
                    throw new Error('missing password encrypted secret for version 2 wallet');
87
                }
88
                if (typeof backupData.password === "undefined") {
89
                    throw new Error('missing primary passphrase for version 2 wallet');
90
                }
91
            } else {
92
                if (typeof backupData.encryptedRecoverySecretMnemonic === "undefined" || !backupData.encryptedRecoverySecretMnemonic) {
93
                    throw new Error('missing encrypted recovery secret for version 2 wallet (recovery without password)');
94
                }
95
                if (!backupData.recoverySecretDecryptionKey) {
96
                    throw new Error('missing recovery secret decryption key for version 2 wallet (recovery without password)');
97
                }
98
            }
99
100
            // cleanup copy paste errors from mnemonics
101
            backupData.encryptedPrimaryMnemonic = backupData.encryptedPrimaryMnemonic.trim()
102
                .replace(new RegExp("\r\n", 'g'), " ")
103
                .replace(new RegExp("\n", 'g'), " ")
104
                .replace(/\s+/g, " ");
105
            backupData.backupMnemonic = (backupData.backupMnemonic || "").trim()
106
                .replace(new RegExp("\r\n", 'g'), " ")
107
                .replace(new RegExp("\n", 'g'), " ")
108
                .replace(/\s+/g, " ");
109
            if (backupData.recoverySecretDecryptionKey) {
110
                backupData.recoverySecretDecryptionKey = backupData.recoverySecretDecryptionKey.trim()
111
                    .replace(new RegExp("\r\n", 'g'), " ")
112
                    .replace(new RegExp("\n", 'g'), " ")
113
                    .replace(/\s+/g, " ");
114
            }
115
            if (usePassword) {
116
                backupData.passwordEncryptedSecretMnemonic = backupData.passwordEncryptedSecretMnemonic.trim()
117
                    .replace(new RegExp("\r\n", 'g'), " ").replace(new RegExp("\n", 'g'), " ").replace(/\s+/g, " ");
118
            } else {
119
                backupData.encryptedRecoverySecretMnemonic = backupData.encryptedRecoverySecretMnemonic.trim()
120
                    .replace(new RegExp("\r\n", 'g'), " ").replace(new RegExp("\n", 'g'), " ").replace(/\s+/g, " ");
121
            }
122
123
        break;
124
125
        default:
126
            throw new Error('Wrong version [' + backupData.walletVersion + ']');
127
    }
128
129
130
    // create BIP32 HDNodes for the Blocktrail public keys
131
    this.blocktrailPublicKeys = {};
132
    _.each(backupData.blocktrailKeys, function(blocktrailKey) {
133
        self.blocktrailPublicKeys[blocktrailKey['keyIndex']] = bitcoin.HDNode.fromBase58(blocktrailKey['pubkey'], self.network);
134
    });
135
136
    // convert the primary and backup mnemonics to seeds (using BIP39)
137
    var primarySeed, backupSeed, secret;
138
    switch (backupData.walletVersion) {
139
        case 1:
140
            primarySeed = bip39.mnemonicToSeed(backupData.primaryMnemonic, backupData.primaryPassphrase);
141
            backupSeed = bip39.mnemonicToSeed(backupData.backupMnemonic, "");
142
        break;
143
144
        case 2:
145
            // convert mnemonics to hex (bip39) and then base64 for decryption
146
            backupData.encryptedPrimaryMnemonic = blocktrail.convert(bip39.mnemonicToEntropy(backupData.encryptedPrimaryMnemonic), 'hex', 'base64');
147
            if (usePassword) {
148
                backupData.passwordEncryptedSecretMnemonic = blocktrail.convert(
149
                    bip39.mnemonicToEntropy(backupData.passwordEncryptedSecretMnemonic), 'hex', 'base64');
150
            } else {
151
                backupData.encryptedRecoverySecretMnemonic = blocktrail.convert(
152
                    bip39.mnemonicToEntropy(backupData.encryptedRecoverySecretMnemonic), 'hex', 'base64');
153
            }
154
155
            // decrypt encryption secret
156
            if (usePassword) {
157
                secret = CryptoJS.AES.decrypt(backupData.passwordEncryptedSecretMnemonic, backupData.password).toString(CryptoJS.enc.Utf8);
158
            } else {
159
                secret = CryptoJS.AES.decrypt(backupData.encryptedRecoverySecretMnemonic, backupData.recoverySecretDecryptionKey).toString(CryptoJS.enc.Utf8);
160
            }
161
162
            if (!secret) {
163
                throw new Error("Could not decrypt secret with " + (usePassword ? "password" : "decryption key"));
164
            }
165
166
            // now finally decrypt the primary seed and convert to buffer (along with backup seed)
167
            primarySeed = new Buffer(CryptoJS.AES.decrypt(backupData.encryptedPrimaryMnemonic, secret).toString(CryptoJS.enc.Utf8), 'base64');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
168
169
            if (backupData.backupMnemonic) {
170
                backupSeed = new Buffer(bip39.mnemonicToEntropy(backupData.backupMnemonic), 'hex');
171
            }
172
173
        break;
174
175
        case 3:
176
            // convert mnemonics to hex (bip39) and then base64 for decryption
177
            backupData.encryptedPrimaryMnemonic = EncryptionMnemonic.decode(backupData.encryptedPrimaryMnemonic);
178
            if (usePassword) {
179
                backupData.passwordEncryptedSecretMnemonic = EncryptionMnemonic.decode(backupData.passwordEncryptedSecretMnemonic);
180
            } else {
181
                backupData.encryptedRecoverySecretMnemonic = EncryptionMnemonic.decode(backupData.encryptedRecoverySecretMnemonic);
182
            }
183
184
            // decrypt encryption secret
185
            if (usePassword) {
186
                secret = Encryption.decrypt(backupData.passwordEncryptedSecretMnemonic, new Buffer(backupData.password));
187
            } else {
188
                secret = Encryption.decrypt(backupData.encryptedRecoverySecretMnemonic, new Buffer(backupData.recoverySecretDecryptionKey, 'hex'));
189
            }
190
191
            if (!secret) {
192
                throw new Error("Could not decrypt secret with " + (usePassword ? "password" : "decryption key"));
193
            }
194
195
            // now finally decrypt the primary seed and convert to buffer (along with backup seed)
196
            primarySeed = Encryption.decrypt(backupData.encryptedPrimaryMnemonic, secret);
197
            if (backupData.backupMnemonic) {
198
                backupSeed = new Buffer(bip39.mnemonicToEntropy(backupData.backupMnemonic), 'hex');
199
            }
200
201
        break;
202
203
        default:
204
            throw new Error('Wrong version [' + backupData.walletVersion + ']');
205
    }
206
207
    // convert the primary and backup seeds to private keys (using BIP32)
208
    this.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(primarySeed, this.network);
209
210
    if (backupSeed) {
211
        this.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(backupSeed, this.network);
212
        this.backupPublicKey = this.backupPrivateKey.neutered();
213
    } else {
214
        this.backupPrivateKey = false;
215
        this.backupPublicKey = bitcoin.HDNode.fromBase58(backupData.backupPublicKey, this.network);
216
    }
217
218
    if (this.settings.logging) {
219
        console.log('using password method: ' + usePassword);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
220
        console.log("Primary Prv Key: " + this.primaryPrivateKey.toBase58());
221
        console.log("Primary Pub Key: " + this.primaryPrivateKey.neutered().toBase58());
222
        console.log("Backup Prv Key: " + (this.backupPrivateKey ? this.backupPrivateKey.toBase58() : null));
223
        console.log("Backup Pub Key: " + this.backupPublicKey.toBase58());
224
    }
225
};
226
227
228
/**
229
 * returns an appropriate bitcoin-js lib network
230
 *
231
 * @param network
232
 * @param testnet
233
 * @returns {*[]}
234
 */
235
WalletSweeper.prototype.getBitcoinNetwork =  function(network, testnet) {
236
    switch (network.toLowerCase()) {
237
        case 'btc':
238
        case 'bitcoin':
239
            if (testnet) {
240
                return bitcoin.networks.testnet;
241
            } else {
242
                return bitcoin.networks.bitcoin;
243
            }
244
        break;
0 ignored issues
show
Unused Code introduced by
This break statement is unnecessary and may be removed.
Loading history...
245
        case 'tbtc':
246
        case 'bitcoin-testnet':
247
            return bitcoin.networks.testnet;
248
        default:
249
            throw new Error("Unknown network " + network);
250
    }
251
};
252
253
/**
254
 * gets the blocktrail pub key for the given path from the stored array of pub keys
255
 *
256
 * @param path
257
 * @returns {boolean}
258
 */
259
WalletSweeper.prototype.getBlocktrailPublicKey = function(path) {
260
    path = path.replace("m", "M");
261
    var keyIndex = path.split("/")[1].replace("'", "");
262
263
    if (!this.blocktrailPublicKeys[keyIndex]) {
264
        throw new Error("Wallet.getBlocktrailPublicKey keyIndex (" + keyIndex + ") is unknown to us");
265
    }
266
267
    return this.blocktrailPublicKeys[keyIndex];
268
};
269
270
/**
271
 * generate multisig address and redeem script for given path
272
 *
273
 * @param path
274
 * @returns {{address: *, redeemScript: *}}
275
 */
276
WalletSweeper.prototype.createAddress = function(path) {
277
    //ensure a public path is used
278
    path = path.replace("m", "M");
279
    var keyIndex = path.split("/")[1].replace("'", "");
280
    var scriptType = parseInt(path.split("/")[2]);
281
282
    //derive the primary pub key directly from the primary priv key
283
    var primaryPubKey = walletSDK.deriveByPath(this.primaryPrivateKey, path, "m");
284
    //derive the backup pub key directly from the backup priv key (unharden path)
285
    var backupPubKey = walletSDK.deriveByPath(this.backupPublicKey, path.replace("'", ""), "M");
286
    //derive a pub key for this path from the blocktrail pub key
287
    var blocktrailPubKey = walletSDK.deriveByPath(this.getBlocktrailPublicKey(path), path, "M/" + keyIndex + "'");
288
289
    //sort the keys and generate a multisig redeem script and address
290
    var multisigKeys = walletSDK.sortMultiSigKeys([
291
        primaryPubKey.keyPair.getPublicKeyBuffer(),
292
        backupPubKey.keyPair.getPublicKeyBuffer(),
293
        blocktrailPubKey.keyPair.getPublicKeyBuffer()
294
    ]);
295
296
    var multisig = bitcoin.script.multisig.output.encode(2, multisigKeys);
297
    var redeemScript, witnessScript;
298
    if (this.network !== "bitcoincash" && scriptType === walletSDK.CHAIN_BTC_SEGWIT) {
299
        witnessScript = multisig;
300
        redeemScript = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(witnessScript));
301
    } else {
302
        witnessScript = null;
303
        redeemScript = multisig;
304
    }
305
    var scriptHash = bitcoin.crypto.hash160(redeemScript);
306
    var scriptPubKey = bitcoin.script.scriptHash.output.encode(scriptHash);
307
308
    var network = this.network;
309
    if (typeof this.network !== "undefined") {
310
        network = this.network;
311
    }
312
    var address = bitcoin.address.fromOutputScript(scriptPubKey, network);
313
314
    //@todo return as buffers
315
    return {address: address.toString(), redeem: redeemScript, witness: witnessScript};
316
};
317
318
/**
319
 * create a batch of multisig addresses
320
 *
321
 * @param start
322
 * @param count
323
 * @param keyIndex
324
 * @param chain
325
 * @returns {{}}
326
 */
327
WalletSweeper.prototype.createBatchAddresses = function(start, count, keyIndex, chain) {
328
    var self = this;
329
    var addresses = {};
330
331
    return q.all(_.range(0, count).map(function(i) {
332
        //create a path subsequent address
333
        var path =  "M/" + keyIndex + "'/" + chain + "/" + (start + i);
334
        var multisig = self.createAddress(path);
335
        addresses[multisig['address']] = {
336
            redeem: multisig['redeem'],
337
            witness: multisig['witness'],
338
            path: path
339
        };
340
    })).then(function() {
341
        return addresses;
342
    });
343
};
344
345
WalletSweeper.prototype.discoverWalletFunds = function(increment, cb) {
346
    var self = this;
347
    var totalBalance = 0;
348
    var totalUTXOs = 0;
349
    var totalAddressesGenerated = 0;
350
    var addressUTXOs = {};    //addresses and their utxos, paths and redeem scripts
351
    if (typeof increment === "undefined") {
352
        increment = this.settings.sweepBatchSize;
353
    }
354
355
    var deferred = q.defer();
356
    deferred.promise.nodeify(cb);
357
358
    var checkChain;
359
    if (this.network === "bitcoincash") {
360
        checkChain = [0, 1];
361
    } else {
362
        checkChain = [0, 1, 2];
363
    }
364
365
    async.nextTick(function() {
366
        //for each blocktrail pub key, do fund discovery on batches of addresses
367
        async.eachSeries(Object.keys(self.blocktrailPublicKeys), function(keyIndex, done) {
368
            async.eachSeries(checkChain, function(chain, done) {
369
                var i = 0;
370
                var hasTransactions = false;
371
372
                async.doWhilst(function(done) {
373
                    //do
374
                    if (self.settings.logging) {
375
                        console.log("generating addresses " + i + " -> " + (i + increment) + " using blocktrail key index: " + keyIndex + ", chain: " + chain);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
376
                    }
377
                    deferred.notify({
378
                        message: "generating addresses " + i + " -> " + (i + increment) + "",
379
                        increment: increment,
380
                        btPubKeyIndex: keyIndex,
381
                        chain: chain,
382
                        //addresses: [],
383
                        totalAddresses: totalAddressesGenerated,
384
                        addressUTXOs: addressUTXOs,
385
                        totalUTXOs: totalUTXOs,
386
                        totalBalance: totalBalance
387
                    });
388
389
                    async.nextTick(function() {
390
                        self.createBatchAddresses(i, increment, keyIndex, chain)
391
                            .then(function(batch) {
392
                                totalAddressesGenerated += Object.keys(batch).length;
393
394
                                if (self.settings.logging) {
395
                                    console.log("starting fund discovery for " + increment + " addresses...");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
396
                                }
397
398
                                deferred.notify({
399
                                    message: "starting fund discovery for " + increment + " addresses",
400
                                    increment: increment,
401
                                    btPubKeyIndex: keyIndex,
402
                                    //addresses: addresses,
403
                                    totalAddresses: totalAddressesGenerated,
404
                                    addressUTXOs: addressUTXOs,
405
                                    totalUTXOs: totalUTXOs,
406
                                    totalBalance: totalBalance
407
                                });
408
409
                                //get the unspent outputs for this batch of addresses
410
                                return self.bitcoinDataClient.batchAddressHasTransactions(_.keys(batch)).then(function(_hasTransactions) {
411
                                    hasTransactions = _hasTransactions;
412
                                    if (self.settings.logging) {
413
                                        console.log("batch " + (hasTransactions ? "has" : "does not have") + " transactions...");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
414
                                    }
415
416
                                    return q.when(hasTransactions)
417
                                        .then(function(hasTransactions) {
418
                                            if (!hasTransactions) {
419
                                                return;
420
                                            }
421
422
                                            //get the unspent outputs for this batch of addresses
423
                                            return self.utxoFinder.getUTXOs(_.keys(batch)).then(function(utxos) {
424
                                                // save the address utxos, along with relevant path and redeem script
425
                                                _.each(utxos, function(outputs, address) {
426
                                                    var witnessScript = null;
427
                                                    if (typeof batch[address]['witness'] !== 'undefined') {
428
                                                        witnessScript = batch[address]['witness'];
429
430
                                                    }
431
                                                    addressUTXOs[address] = {
432
                                                        path: batch[address]['path'],
433
                                                        redeem: batch[address]['redeem'],
434
                                                        witness: witnessScript,
435
                                                        utxos: outputs
436
                                                    };
437
438
                                                    totalUTXOs += outputs.length;
439
440
                                                    //add up the total utxo value for all addresses
441
                                                    totalBalance = _.reduce(outputs, function(carry, output) {
442
                                                        return carry + output['value'];
443
                                                    }, totalBalance);
444
445
                                                    if (self.settings.logging) {
446
                                                        console.log("found " + outputs.length + " unspent outputs for address: " + address);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
447
                                                    }
448
                                                });
449
450
                                                deferred.notify({
451
                                                    message: "discovering funds",
452
                                                    increment: increment,
453
                                                    btPubKeyIndex: keyIndex,
454
                                                    totalAddresses: totalAddressesGenerated,
455
                                                    addressUTXOs: addressUTXOs,
456
                                                    totalUTXOs: totalUTXOs,
457
                                                    totalBalance: totalBalance
458
                                                });
459
                                            });
460
                                        })
461
                                        ;
462
                                });
463
                            })
464
                            .then(
465
                                function() {
466
                                    //ready for the next batch
467
                                    i += increment;
468
                                    async.nextTick(done);
469
                                },
470
                                function(err) {
471
                                    done(err);
472
                                }
473
                            )
474
                        ;
475
                    });
476
                }, function() {
477
                    //while
478
                    return hasTransactions;
479
                }, function(err) {
480
                    //all done
481
                    if (err) {
482
                        console.log("batch complete, but with errors", err.message);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
483
484
                        deferred.notify({
485
                            message: "batch complete, but with errors: " + err.message,
486
                            error: err,
487
                            increment: increment,
488
                            btPubKeyIndex: keyIndex,
489
                            totalAddresses: totalAddressesGenerated,
490
                            addressUTXOs: addressUTXOs,
491
                            totalUTXOs: totalUTXOs,
492
                            totalBalance: totalBalance
493
                        });
494
                    }
495
                    //ready for next Blocktrail pub key
496
                    async.nextTick(done);
497
                });
498
            }, function(err) {
499
                done(err);
500
            });
501
        }, function(err) {
502
            //callback
503
            if (err) {
504
                //perhaps we should also reject the promise, and stop everything?
505
                if (self.settings.logging) {
506
                    console.log("error encountered when discovering funds", err);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
507
                }
508
            }
509
510
            if (self.settings.logging) {
511
                console.log("finished fund discovery: " + totalBalance + " Satoshi (in " + totalUTXOs + " outputs) " +
512
                    "found when searching " + totalAddressesGenerated + " addresses");
513
            }
514
515
            self.sweepData = {
516
                utxos: addressUTXOs,
517
                count: totalUTXOs,
518
                balance: totalBalance,
519
                addressesSearched: totalAddressesGenerated
520
            };
521
522
            //resolve the promise
523
            deferred.resolve(self.sweepData);
524
        });
525
    });
526
527
    return deferred.promise;
528
};
529
530
WalletSweeper.prototype.sweepWallet = function(destinationAddress, cb) {
531
    var self = this;
532
    var deferred = q.defer();
533
    deferred.promise.nodeify(cb);
534
535
    if (self.settings.logging) {
536
        console.log("starting wallet sweeping to address " + destinationAddress);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
537
    }
538
539
    q.when(true)
540
        .then(function() {
541
            if (!self.sweepData) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !self.sweepData is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
542
                //do wallet fund discovery
543
                return self.discoverWalletFunds()
544
                    .progress(function(progress) {
545
                        deferred.notify(progress);
546
                    });
547
            }
548
        })
549
        .then(function() {
550
            return self.bitcoinDataClient.estimateFee();
551
        })
552
        .then(function(feePerKb) {
553
            if (self.sweepData['balance'] === 0) {
554
                //no funds found
555
                deferred.reject("No funds found after searching through " + self.sweepData['addressesSearched'] + " addresses");
556
                return deferred.promise;
557
            }
558
559
            //create and sign the transaction
560
            return self.createTransaction(destinationAddress, null, feePerKb, deferred);
561
        })
562
        .then(function(r) {
563
            deferred.resolve(r);
564
        }, function(e) {
565
            deferred.reject(e);
566
        });
567
568
    return deferred.promise;
569
};
570
571
/**
572
 * creates a raw transaction from the sweep data
573
 * @param destinationAddress        the destination address for the transaction
574
 * @param fee                       a specific transaction fee to use (optional: if null, fee will be estimated)
575
 * @param feePerKb                  fee per kb (optional: if null, use default value)
576
 * @param deferred                  a deferred promise object, used for giving progress updates (optional)
577
 */
578
WalletSweeper.prototype.createTransaction = function(destinationAddress, fee, feePerKb, deferred) {
579
    var self = this;
580
    if (this.settings.logging) {
581
        console.log("Creating transaction to address destinationAddress");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
582
    }
583
    if (deferred) {
584
        deferred.notify({
585
            message: "creating raw transaction to " + destinationAddress
586
        });
587
    }
588
589
    // create raw transaction
590
    var rawTransaction = new bitcoin.TransactionBuilder(this.network);
591
    if (this.settings.bitcoinCash) {
592
        rawTransaction.enableBitcoinCash();
593
    }
594
    var inputs = [];
595
    _.each(this.sweepData['utxos'], function(data, address) {
596
        _.each(data.utxos, function(utxo) {
597
            rawTransaction.addInput(utxo['hash'], utxo['index']);
598
            inputs.push({
599
                txid:         utxo['hash'],
600
                vout:         utxo['index'],
601
                scriptPubKey: utxo['script_hex'],
602
                value:        utxo['value'],
603
                address:      address,
604
                path:         data['path'],
605
                redeemScript: data['redeem'],
606
                witnessScript: data['witness']
607
            });
608
        });
609
    });
610
    if (!rawTransaction) {
611
        throw new Error("Failed to create raw transaction");
612
    }
613
614
    var sendAmount = self.sweepData['balance'];
615
    var outputIdx = rawTransaction.addOutput(destinationAddress, sendAmount);
616
617
    if (typeof fee === "undefined" || fee === null) {
618
        //estimate the fee and reduce it's value from the output
619
        if (deferred) {
620
            deferred.notify({
621
                message: "estimating transaction fee, based on " + blocktrail.toBTC(feePerKb) + " BTC/kb"
622
            });
623
        }
624
        var calcUtxos = inputs.map(function(input) {
625
            return {
626
                txid: input.txid,
627
                vout: input.vout,
628
                address: input.vout,
629
                scriptpubkey_hex: input.vout,
630
                redeem_script: input.redeemScript,
631
                witness_script: input.witnessScript,
632
                path: input.path,
633
                value: input.value
634
            };
635
        });
636
        fee = walletSDK.estimateVsizeFee(rawTransaction.tx, calcUtxos, feePerKb);
637
    }
638
    rawTransaction.tx.outs[outputIdx].value -= fee;
639
640
    //sign and return the raw transaction
641
    if (deferred) {
642
        deferred.notify({
643
            message: "signing transaction"
644
        });
645
    }
646
    return this.signTransaction(rawTransaction, inputs);
647
};
648
649
WalletSweeper.prototype.signTransaction = function(rawTransaction, inputs) {
650
    var self = this;
651
    if (this.settings.logging) {
652
        console.log("Signing transaction");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
653
    }
654
655
    var sigHash = bitcoin.Transaction.SIGHASH_ALL;
656
    if (this.settings.bitcoinCash) {
657
        sigHash |= bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143;
658
    }
659
660
    //sign the transaction with the private key for each input
661
    _.each(inputs, function(input, index) {
662
        //create private keys for signing
663
        var primaryPrivKey =  walletSDK.deriveByPath(self.primaryPrivateKey, input['path'].replace("M", "m"), "m").keyPair;
664
        rawTransaction.sign(index, primaryPrivKey, input['redeemScript'], sigHash, input['value'], input['witnessScript']);
665
666
        if (self.backupPrivateKey) {
667
            var backupPrivKey = walletSDK.deriveByPath(self.backupPrivateKey, input['path'].replace("'", "").replace("M", "m"), "m").keyPair;
668
            rawTransaction.sign(index, backupPrivKey, input['redeemScript'], sigHash, input['value'], input['witnessScript']);
669
        }
670
    });
671
672
    if (self.backupPrivateKey) {
673
        return rawTransaction.build().toHex();
674
    } else {
675
        return rawTransaction.buildIncomplete().toHex();
676
    }
677
};
678
679
module.exports = WalletSweeper;
680